package com.gungnir.trial.mapper;

import com.gungnir.trial.entity.BaseDocument;
import com.tyrfing.exceptions.TyrfingServiceException;
import com.tyrfing.model.Pagination;
import com.tyrfing.mongo.TyrfingMongoDb;
import com.tyrfing.mongo.exception.TyrfingMongoException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.GenericTypeResolver;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class LogicalMapper<T extends BaseDocument> {

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private final TyrfingMongoDb tyrfingMongoDb;

    protected LogicalMapper(TyrfingMongoDb tyrfingMongoDb) {
        this.tyrfingMongoDb = tyrfingMongoDb;
    }

    public MongoTemplate getTemplate() throws TyrfingMongoException {
        return tyrfingMongoDb.getTemplate();
    }


    @SuppressWarnings("unchecked")
    protected Class<T> getClazz() {
        return (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), LogicalMapper.class);
    }

    public T getById(String id) throws TyrfingServiceException {
        if (StringUtils.isBlank(id)) {
            throw TyrfingServiceException.lackOfParams();
        }
        T t;
        try {
            t = getTemplate().findById(id, getClazz());
        } catch (TyrfingMongoException e) {
            throw TyrfingServiceException.dataNotFound();
        }
        if (t == null) {
            throw TyrfingServiceException.dataNotFound();
        }
        return t;
    }

    public T findById(String id) {
        T t;
        try {
            t = getTemplate().findById(id, getClazz());
        } catch (TyrfingMongoException e) {
            return null;
        }
        if (t == null || Boolean.TRUE.equals(t.getDeleted())) {
            return null;
        }
        return t;
    }

    public List<T> list() {
        return list(new Query());
    }

    public List<T> list(Query query) {
        try {
            return getTemplate().find(logicQuery(query), getClazz());
        } catch (TyrfingMongoException e) {
            log.error("Mongo查询失败",e);
            return new ArrayList<>();
        }
    }

    public Pagination<T> page(Long current, Long size) {
        return page(current, size, new Query());
    }

    public Pagination<T> page(Long pageNum, Long size, Query query) {
        Query q = logicQuery(query);
        long total = 0;
        List<T> data;
        try {
            total = getTemplate().count(q, getClazz());
            data = getTemplate().find(q.with(PageRequest.of(pageNum.intValue(), size.intValue())), getClazz());
        } catch (TyrfingMongoException e) {
            log.error("Mongo查询失败",e);
            data = new ArrayList<>();
        }
        return toPage(data, pageNum, size, total);
    }

    public <R extends Serializable> Pagination<R> page(Long current, Long size, Function<? super T, ? extends R> mapper) {
        return page(current, size, new Query(), mapper);
    }

    public <R extends Serializable> Pagination<R> page(Long pageNum, Long size, Query query, Function<? super T, ? extends R> mapper) {
        Query q = logicQuery(query);
        long total = 0;
        List<R> data;
        try {
            total = getTemplate().count(q, getClazz());
            data = getTemplate().find(q.with(PageRequest.of(pageNum.intValue(), size.intValue())), getClazz()).stream().map(mapper).collect(Collectors.toList());
        } catch (TyrfingMongoException e) {
            data = new ArrayList<>();
        }
        return toPage(data, pageNum, size, total);
    }

    private <R extends Serializable> Pagination<R> toPage(List<R> data, Long pageNum, Long pageSize, Long total) {
        Pagination<R> pagination = new Pagination<>();
        pagination.setPageSize(pageSize.intValue());
        pagination.setPageNum(pageNum.intValue());
        pagination.setTotal(total);
        pagination.setData(data);
        return pagination;
    }

    /**
     * 新增/更新，能把已经删除的恢复成未删除的，但需要自己传参
     * @param t
     * @return
     * @throws TyrfingServiceException
     */
    public T save(T t) throws TyrfingServiceException {
        if (t == null) {
            throw TyrfingServiceException.lackOfParams();
        }
        if (StringUtils.isBlank(t.getId())) {
            return insert(t);
        }
        T t1 = getById(t.getId());
        //保持创建时间不变
        t.setInsertTime(t1.getInsertTime());
        t.setUpdateTime(System.currentTimeMillis());
        check(t);
        try {
            return getTemplate().save(t);
        } catch (TyrfingMongoException e) {
            log.error("Mongo保存失败",e);
            throw TyrfingServiceException.someThingWentWrong();
        }
    }

    public T insert(T t) throws TyrfingServiceException {
        if (t == null) {
            throw TyrfingServiceException.lackOfParams();
        }
        if (StringUtils.isNotEmpty(t.getId()) || Boolean.TRUE.equals(t.getDeleted())) {
            throw TyrfingServiceException.someThingWentWrong();
        }
        long timeMillis = System.currentTimeMillis();
        t.setInsertTime(timeMillis);
        t.setUpdateTime(timeMillis);
        t.setDeleted(false);
        check(t);
        try {
            return getTemplate().save(t);
        } catch (TyrfingMongoException e) {
            log.error("Mongo新增失败",e);
            throw TyrfingServiceException.someThingWentWrong();
        }
    }

    public T getOne(Query query) throws TyrfingServiceException {
        T t;
        try {
            t = getTemplate().findOne(logicQuery(query), getClazz());
        } catch (TyrfingMongoException e) {
            log.error("Mongo查询失败",e);
            throw TyrfingServiceException.dataNotFound();
        }
        if (t == null) {
            throw TyrfingServiceException.dataNotFound();
        }
        return t;
    }

    public T findOne(Query query) {
        try {
            return getTemplate().findOne(logicQuery(query), getClazz());
        } catch (TyrfingMongoException e) {
            return null;
        }
    }

    public T remove(String id) throws TyrfingServiceException {
        if (StringUtils.isBlank(id)) {
            throw TyrfingServiceException.dataNotFound();
        }
        T t = getById(id);
        t.setDeleteTime(System.currentTimeMillis());
        t.setDeleted(true);
        try {
            return getTemplate().save(t);
        } catch (TyrfingMongoException e) {
            throw TyrfingServiceException.someThingWentWrong();
        }
    }

    public Long count(Query query) {
        try {
            return getTemplate().count(logicQuery(query), getClazz());
        } catch (TyrfingMongoException e) {
            return 0L;
        }
    }

    public Long count() {
        return count(new Query());
    }

    public boolean exists(Query query) {
        try {
            return getTemplate().exists(logicQuery(query), getClazz());
        } catch (TyrfingMongoException e) {
            return false;
        }
    }

    public boolean exists(String id) {
        try {
            getById(id);
            return true;
        } catch (TyrfingServiceException e) {
            return false;
        }
    }

    public Query logicQuery(Query query) {
        return query.addCriteria(Criteria.where("deleted").ne(true));
    }

    public void check(T t) throws TyrfingServiceException {
    }

    public String name() {
        return "";
    }

    public boolean removeAll(Query query) {
        try {
            Update update = Update.update("deleteTime", System.currentTimeMillis());
            update.set("deleted",true);
            getTemplate().updateMulti(logicQuery(query), update, getClazz());
            return true;
        } catch (TyrfingMongoException e) {
            e.fillInStackTrace();
            return false;
        }
    }

    public boolean update(Query query, Update update){
        try {
            getTemplate().updateMulti(logicQuery(query),update,getClazz());
            return true;
        } catch (TyrfingMongoException e) {
            e.fillInStackTrace();
            return false;
        }
    }

}
